﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Practices.SharePoint.Common;
using Microsoft.Practices.SharePoint.Common.Configuration;
using Microsoft.Practices.SharePoint.Common.Logging;
using Microsoft.Practices.SharePoint.Common.ServiceLocation;

using Microsoft.SharePoint.Administration;

namespace SoftwareCraft.SharePoint.Diagnostics.Demo
{
    /// <summary>
    /// Define <see cref="DiagnosticsArea"/>s and <see cref="DiagnosticsCategory"/>s, standard event messages, and provide various log methods.
    /// </summary>
    public class Diagnostics
    {
        #region Constants
        private const string DIAGNOSTICS_AREA_NAME = "SoftwareCraft (Demo)";
        private static ILogger logger;

        /// <summary>
        /// Diagnostics categories under which events can be written to the trace log file.
        /// </summary>
        //TODO: Replace with names and descriptions of actual systems, functional areas, etc in business solution
        public enum Category
        {
            /// <summary>
            /// Subsystem1 name and description
            /// </summary>
            Subsystem1,
            /// <summary>
            /// Subsystem2 name and description
            /// </summary>
            Subsystem2,
            /// <summary>
            /// Subsystem2 name and description
            /// </summary>
            Subsystem3,
            /// <summary>
            /// FunctionalArea1 name and description
            /// </summary>
            FunctionalArea1,
            /// <summary>
            /// FunctionalArea2 name and description
            /// </summary>
            FunctionalArea2,
            /// <summary>
            /// FunctionalArea3 name and description
            /// </summary>
            FunctionalArea3
        }

        //TODO: Add predefined event messages
        private static readonly Dictionary<EventId, string> EventMessage = new Dictionary<EventId, string> 
        {
            {EventId.General, "{0}"}
        };

        /// <summary>
        /// Event ids for pre-defined messages with substitution parameters that can be written to the trace log file.
        /// </summary>
        //TODO: Add event ids for each predefined event message
        public enum EventId
        {
            /// <summary>
            /// General purpose event with no pre-defined message, only user supplied details
            /// </summary>
            General = 15000
        };
        #endregion

        #region Logging
        /// <summary>
        /// Writes an event with a related exception to the trace log file.
        /// </summary>
        /// <param name="cat">Diagnostics <see cref="Category"/> under which to write the event.</param>
        /// <param name="severity"><see cref="TraceSeverity"/> to apply to this event.</param>
        /// <param name="eventId"><see cref="EventId"/> of the event to write to the trace log file.</param>
        /// <param name="ex">Related <see cref="System.Exception"/> to include with the event.</param>
        /// <param name="details">Additional user-supplied details to include with the event.</param>
        public static void Log(Category cat, TraceSeverity severity, EventId eventId, Exception ex, string details)
        {
            if (logger == null)
                logger = SharePointServiceLocator.GetCurrent().GetInstance<ILogger>();

            // Argument category is really area and category path. Does the Guidance library have a function to format this correctly?
            logger.TraceToDeveloper(ex, string.Format(EventMessage[eventId], details), (int)eventId, severity, CategoryPath(cat));
        }
        /// <summary>
        /// Writes an event to the trace log file.
        /// </summary>
        /// <param name="cat">Diagnostics <see cref="Category"/> under which to write the event.</param>
        /// <param name="severity"><see cref="TraceSeverity"/> to apply to this event.</param>
        /// <param name="eventId"><see cref="EventId"/> of the event to write to the trace log file.</param>
        /// <param name="details">Additional user-supplied details to include with the event.</param>
        public static void Log(Category cat, TraceSeverity severity, EventId eventId, string details)
        {
            if (logger == null)
                logger = SharePointServiceLocator.GetCurrent().GetInstance<ILogger>();

            // Argument category is really area and category path. Does the Guidance library have a function to format this correctly?
            logger.TraceToDeveloper(string.Format(EventMessage[eventId], details), (int)eventId, severity, CategoryPath(cat));
        }
        /// <summary>
        /// Writes a general error event to the trace log file.
        /// </summary>
        /// <param name="cat">Diagnostics <see cref="Category"/> under which to write the event.</param>
        /// <param name="details">Additional user-supplied details to include with the event.</param>
        public static void LogError(Category cat, string details)
        {
            LogError(cat, EventId.General, details);
        }
        /// <summary>
        /// Writes an error event to the trace log file.
        /// </summary>
        /// <param name="cat">Diagnostics <see cref="Category"/> under which to write the event.</param>
        /// <param name="eventId"><see cref="EventId"/> of the event to write to the trace log file.</param>
        /// <param name="details">Additional user-supplied details to include with the event.</param>
        public static void LogError(Category cat, EventId eventId, string details)
        {
            Log(cat, TraceSeverity.Unexpected, eventId, details);
        }
        /// <summary>
        /// Writes a general error event with a related exception to the trace log file.
        /// </summary>
        /// <param name="cat">Diagnostics <see cref="Category"/> under which to write the event.</param>
        /// <param name="ex">Related <see cref="System.Exception"/> to include with the event.</param>
        /// <param name="details">Additional user-supplied details to include with the event.</param>
        public static void LogError(Category cat, Exception ex, string details)
        {
            Log(cat, TraceSeverity.Unexpected, EventId.General, ex, details);
        }
        /// <summary>
        /// Writes an error event with a related exception to the trace log file.
        /// </summary>
        /// <param name="cat">Diagnostics <see cref="Category"/> under which to write the event.</param>
        /// <param name="eventId"><see cref="EventId"/> of the event to write to the trace log file.</param>
        /// <param name="ex">Related <see cref="System.Exception"/> to include with the event.</param>
        /// <param name="details">Additional user-supplied details to include with the event.</param>
        public static void LogError(Category cat, EventId eventId, Exception ex, string details)
        {
            Log(cat, TraceSeverity.Unexpected, eventId, ex, details);
        }
        /// <summary>
        /// Writes a general verbose event to the trace log file.
        /// </summary>
        /// <param name="cat">Diagnostics <see cref="Category"/> under which to write the event.</param>
        /// <param name="details">Additional user-supplied details to include with the event.</param>
        public static void LogVerbose(Category cat, string details)
        {
            LogVerbose(cat, EventId.General, details);
        }
        /// <summary>
        /// Writes a verbose event to the trace log file.
        /// </summary>
        /// <param name="cat">Diagnostics <see cref="Category"/> under which to write the event.</param>
        /// <param name="eventId"><see cref="EventId"/> of the event to write to the trace log file.</param>
        /// <param name="details">Additional user-supplied details to include with the event.</param>
        public static void LogVerbose(Category cat, EventId eventId, string details)
        {
            Log(cat, TraceSeverity.Verbose, eventId, details);
        }
        /// <summary>
        /// Writes a general warning event to the trace log file.
        /// </summary>
        /// <param name="cat">Diagnostics <see cref="Category"/> under which to write the event.</param>
        /// <param name="details">Additional user-supplied details to include with the event.</param>
        public static void LogWarning(Category cat, string details)
        {
            LogWarning(cat, EventId.General, details);
        }
        /// <summary>
        /// Writes a warning event to the trace log file.
        /// </summary>
        /// <param name="cat">Diagnostics <see cref="Category"/> under which to write the event.</param>
        /// <param name="eventId"><see cref="EventId"/> of the event to write to the trace log file.</param>
        /// <param name="details">Additional user-supplied details to include with the event.</param>
        public static void LogWarning(Category cat, EventId eventId, string details)
        {
            Log(cat, TraceSeverity.Medium, eventId, details);
        }
        // Use MS P&P diagnostics area to log during deployment as our diagnostics area is not yet defined or has been removed
        private static void LogDeployment(string details)
        {
            var logger = SharePointServiceLocator.GetCurrent().GetInstance<ILogger>();
            logger.TraceToDeveloper(details, 15999, TraceSeverity.Medium, SharePointLogger.DefaultCategory);
        }
        #endregion

        #region Deployment
        /// <summary>
        /// Add the SoftwareCraft diagnostics area and its categories to the farm.
        /// </summary>
        internal static void AddArea()
        {
            LogDeployment("Add diagnostics area and its categories to farm.");

            RemoveArea();

            LogDeployment(string.Format("Create '{0}' diagnostics area and categories.", DIAGNOSTICS_AREA_NAME));

            var area = new DiagnosticsArea(DIAGNOSTICS_AREA_NAME);

            var categories = area.DiagnosticsCategories;

            //TODO: Replace with actual categories and appropriate default throttle value for TraceSeverity and EventSeverity
            categories.Add(new DiagnosticsCategory(Category.Subsystem1.ToString(), EventSeverity.Verbose, TraceSeverity.Verbose));
            categories.Add(new DiagnosticsCategory(Category.Subsystem2.ToString(), EventSeverity.Warning, TraceSeverity.Medium));
            categories.Add(new DiagnosticsCategory(Category.Subsystem3.ToString(), EventSeverity.Error, TraceSeverity.High));
            categories.Add(new DiagnosticsCategory(Category.FunctionalArea1.ToString(), EventSeverity.Information, TraceSeverity.VerboseEx));
            categories.Add(new DiagnosticsCategory(Category.FunctionalArea2.ToString(), EventSeverity.Warning, TraceSeverity.Monitorable));
            categories.Add(new DiagnosticsCategory(Category.FunctionalArea3.ToString(), EventSeverity.ErrorCritical, TraceSeverity.Unexpected));

            var areas = CurrentAreas();

            LogDeployment(string.Format("Add '{0}' diagnostics area and its categories.", DIAGNOSTICS_AREA_NAME));

            areas.Add(area);

            LogDeployment("Save diagnostics configuration.");

            areas.SaveConfiguration();

            LogDeployment("Ensure diagnostics area registered as event source.");

            DiagnosticsAreaEventSource.EnsureConfiguredAreasRegistered();
        }
        /// <summary>
        /// Remove the SoftwareCraft diagnostics area and all its categories from the farm.
        /// </summary>
        internal static void RemoveArea()
        {
            LogDeployment("Remove diagnostics area and its categories from farm.");

            var areas = CurrentAreas();

            if (!areas.Contains(DIAGNOSTICS_AREA_NAME))
            {
                LogDeployment(string.Format("'{0}' diagnostics area not found on farm.", DIAGNOSTICS_AREA_NAME));

                return;
            }

            LogDeployment(string.Format("Remove '{0}' diagnostics area and its categories.", DIAGNOSTICS_AREA_NAME));

            areas.RemoveAt(areas.IndexOf(areas[DIAGNOSTICS_AREA_NAME]));

            LogDeployment("Save diagnostics configuration.");

            areas.SaveConfiguration();
        }
        #endregion

        #region Helper Methods
        public static string CategoryPath(Category category)
        {
            return DIAGNOSTICS_AREA_NAME + Constants.CategoryPathSeparator + category;
        }
        private static DiagnosticsAreaCollection CurrentAreas()
        {
            var locator = SharePointServiceLocator.GetCurrent();
            
            var config = locator.GetInstance<IConfigManager>();

            return new DiagnosticsAreaCollection(config);
        }
        #endregion
    }
}
